#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Xml;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using InputEventSystem;
using SkinnedModel;
using MorphTarget;
using SpeechLib;
using ConfigReader;
using Primitives3D;
using XNAViewerSample.Forms;
#endregion

namespace XNAViewerSample
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public partial class XNAViewer : Microsoft.Xna.Framework.Game
    {
        #region Fields
        private enum changedEnum { None, Mesh, Hair, Animation, Accessory, Cloth, ClothType, Dance, Beat, Expression };
        private struct changedStruct
        {
            public changedEnum Type;
            public List<String> Param;

            public changedStruct(changedEnum type)
            {
                this.Type = type;
                this.Param = new List<string>();
                this.Param.Clear();
            }
        }
        private changedStruct modelChangedItem = new changedStruct(changedEnum.None);

        // Fadeout effect
        private bool bScreencap = false;
        private bool bScreenfade = false;
        RenderTarget2D rt = null;
        int alpha = 256;

        // Demo
        private bool bDemo = true;

        #endregion

        #region Events
        #endregion

        #region Threads
        #endregion

        #region Callbacks and Handlers

        /// <summary>
        /// Generic handler for any model changes
        /// Called by Update method after screencap
        /// </summary>
        private void ModelChangedHandler()
        {
            switch (modelChangedItem.Type)
            {
                case changedEnum.Hair:
                    ModelHairChanged(modelChangedItem.Param[0]);
                    break;
                case changedEnum.Animation:
                    ModelAnimationChanged(modelChangedItem.Param[0]);
                    break;
                case changedEnum.Accessory:
                    ModelAccessoryChanged(modelChangedItem.Param[0], modelChangedItem.Param[1]);
                    break;
                case changedEnum.Cloth:
                    ModelClothChanged(modelChangedItem.Param[0], modelChangedItem.Param[1], modelChangedItem.Param[2]);
                    break;
                case changedEnum.ClothType:
                    ModelClothTypeChanged(modelChangedItem.Param[0], modelChangedItem.Param[1]);
                    break;
                case changedEnum.Mesh:
                    ModelMeshChanged(modelChangedItem.Param[0]);
                    ListConverter.SetList("Expression", Panel_modelMorphHandler(modelHook, false, String.Empty, String.Empty));
                    ListConverter.SetList("Viseme", Panel_modelVisemeHandler(modelHook, String.Empty, String.Empty));
                    break;
                case changedEnum.Dance:
                    ModelDanceChanged();
                    break;
                case changedEnum.Beat:
                    // revert back to beat
                    ModelAnimationChanged("Beat");
                    // reenable breast physics
                    foreach (Physics.SoftBodyDynamics sbd in modelManager.SkinModel[modelHook + "BodyA"].SoftBodyDynamics.Values)
                        sbd.IsEnabled = true;
                    // hide skirt as there's no associated animation
                    ModelClothChanged(String.Empty, SelectedModel.ctype.Skirt.ToString(), "Off");
                    break;
                case changedEnum.Expression:
                    ModelExpressionChanged(modelChangedItem.Param[0]);
                    break;
            }

            modelChangedItem.Type = changedEnum.None;
            modelChangedItem.Param.Clear();
        }

        #region Media Player handlers
        private string GetHumanReadableTime(TimeSpan timeSpan)
        {
            int minutes = timeSpan.Minutes;
            int seconds = timeSpan.Seconds;

            if (seconds < 10)
                return minutes + ":0" + seconds;
            else
                return minutes + ":" + seconds;
        }

        private void MediaPlayer_MediaStateChanged(object sender, EventArgs e)
        {
            if (MediaPlayer.State == MediaState.Stopped)
            {
                if (songList.Count > 0) songList.RemoveAt(0);
                if (songList.Count > 0)
                    MediaPlayer.Play(songList[0]);
                else
                {
                    bScreencap = true;
                    modelChangedItem.Type = changedEnum.Beat;
                }
            }
        }
        #endregion

        #region Drag n Drop handlers
        private static bool IsAudio(string file)
        {
            var ext = Path.GetExtension(file).ToLower();
            return ext == ".wav" || ext == ".mp3" || ext == ".wma";
        }

        private void f_DragOver(object sender, System.Windows.Forms.DragEventArgs e)
        {
            e.Effect = System.Windows.Forms.DragDropEffects.None;
            if (e.Data.GetDataPresent(System.Windows.Forms.DataFormats.FileDrop))
            {
                string[] files = e.Data.GetData(System.Windows.Forms.DataFormats.FileDrop, false) as String[];
                foreach (var f in files)
                {
                    if ((IsAudio(f)) && controlPanel.AcceptDrop())
                    {
                        e.Effect = System.Windows.Forms.DragDropEffects.Copy;
                        return;
                    }
                }
            }
        }

        private void f_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
        {
            string[] files = e.Data.GetData(System.Windows.Forms.DataFormats.FileDrop, false) as String[];
            foreach (var f in files)
            {
                if ((IsAudio(f)) && (controlPanel.AcceptDrop()))
                {
                    //Forms.Helpers.MessageBox(f);
                    try
                    {
                        String title = Path.GetFileNameWithoutExtension(f).Trim();
                        if (title.Length > 16)
                            title = title.Substring(0, 16) + "...";
                        Song song = Song.FromUri(title, new Uri(f));
                        songList.Add(song);

                        if ((songList.Count == 1) && MediaPlayer.State == MediaState.Stopped)
                        {
                            // this is the first song, play it
                            MediaPlayer.Play(songList[0]);
                            // load first dance animation
                            bScreencap = true;
                            modelChangedItem.Type = changedEnum.Dance;
                        }
                    }
                    catch
                    {
                        Forms.Helpers.MessageBox("Song not imported. (e.g. Song.FromUri method can not" + Environment.NewLine +
                                                 "import song with whitespaces in path and filename." + Environment.NewLine + 
                                                 "This is XNA bug, not EskNA's.)");
                    }

                    return;
                }
            }
        }

        #endregion

        #region Speech/Voice/Chat handlers
        private void Panel_chatInputHandler(string text)
        {
            if (String.IsNullOrEmpty(text)) return;
            if (String.IsNullOrWhiteSpace(text)) return;

            // add request to log
            controlPanel.AddLog(text, true);

            // send request to chatbot: TODO
            string response;
            CSkinnedModel mdl = modelManager.SkinModel[modelHook + "Head"];
            if (mdl.Personality.EngineType != Personality.AIType.UNKNOWN)
                response = mdl.Personality.GetResponse(text.Trim());
            else
                response = text.Trim();

            // display response to log
            ResponseType type;
            if (!String.IsNullOrEmpty(response))
                type = controlPanel.AddLog(response, false);
            else
                type = controlPanel.AddLog("No response found.", false);

            // speak if voice is active: TODO
            if (avatarVoice != null)
            {
                avatarVoice.Speak(controlPanel.LogText, SpeechVoiceSpeakFlags.SVSFlagsAsync);
                //morphTarget.StartPlayList("Viseme", true);
            }
        }

        private void Panel_voiceOptionHandler(byte options)
        {
            int voicePitch = 0;
            if ((options & 0x01) == 0x01)
            {
                // change in voice
                avatarVoice.Voice = spToken.Item(controlPanel.GetVoiceIndex());
            }
            if ((options & 0x02) == 0x02)
            {
                // change in volume
                avatarVoice.Volume = controlPanel.Volume;
            }
            if ((options & 0x04) == 0x04)
            {
                // change in rate
                avatarVoice.Rate = controlPanel.Rate;
            }
            if ((options & 0x08) == 0x08)
            {
                // change in pitch
                voicePitch = controlPanel.Pitch;
            }
            if (options != 0)
            {
                if (options < 0x08)
                    avatarVoice.Speak(avatarVoice.Voice.GetDescription(0), SpeechVoiceSpeakFlags.SVSFlagsAsync);
                else
                    avatarVoice.Speak("<pitch absmiddle='" + voicePitch.ToString() + "'/>" + avatarVoice.Voice.GetDescription(0), 
                        SpeechVoiceSpeakFlags.SVSFlagsAsync | SpeechVoiceSpeakFlags.SVSFPersistXML);
            }
        }

        /// <summary>
        /// Speech library callback when a Viseme event occurs
        /// </summary>
        /// <param name="StreamNumber">Stream number (unused)</param>
        /// <param name="StreamPosition">Stream position (unused)</param>
        /// <param name="Duration">Viseme duration (always 0?)</param>
        /// <param name="NextVisemeId">Next Viseme ID (always SVP_0?)</param>
        /// <param name="Feature">Speech feature (always Emphasis?)</param>
        /// <param name="CurrentVisemeId">Current Viseme ID</param>
        private void avatarVoice_Viseme(int StreamNumber, object StreamPosition, int Duration, SpeechVisemeType NextVisemeId, SpeechVisemeFeature Feature, SpeechVisemeType CurrentVisemeId)
        {
            //if (!modelManager.SkinModel.ContainsKey(modelHook + "Head")) return;

            // NB: very important, add new viseme first, before stopping previous
            // or else morph player will think it is already end of sequence
            string vmapped = "V" + modelManager.SkinModel[modelHook + "Head"].VisemeMap[(int)CurrentVisemeId].ToString("D2");
            foreach (CMorphTarget mt in modelManager.SkinModel[modelHook + "Head"].MorphTargets.Values)
            {
                if (mt.Sequence.ContainsKey("Viseme"))
                {
                    if (!mt.IsEnabled("Viseme"))  // this is first in playlist
                        mt.StartPlayList("Viseme", true);

                    mt.AddList(vmapped, "Viseme");
                    // stop previous morph
                    if (mt.Sequence["Viseme"].Count > 1)
                        mt.Sequence["Viseme"].ElementAt(mt.Sequence["Viseme"].Count - 2).Stop(true);
                }
            }
        }

        /// <summary>
        /// Speech library callback when an end of stream event occurs
        /// </summary>
        /// <param name="StreamNumber">Stream number</param>
        /// <param name="StreamPosition">Ending stream position</param>
        private void avatarVoice_EndStream(int StreamNumber, object StreamPosition)
        {
            // no need to check running state, but just to be sure
            if (avatarVoice.Status.RunningState == SpeechRunState.SRSEDone)
            {
                //if (!modelManager.SkinModel.ContainsKey(modelHook + "Head")) return;
                // play the last viseme prior to resetting
                // this is usually silence stream, but SAPI5 voices are not consistent (1~2 silence)
                foreach (CMorphTarget mt in modelManager.SkinModel[modelHook + "Head"].MorphTargets.Values)
                {
                    if (mt.Sequence.ContainsKey("Viseme"))
                    {
                        int count = mt.Sequence["Viseme"].Count;
                        mt.Sequence["Viseme"].ElementAt(count - 1).Stop(true);
                    }
                }
#if false
                // other SAPI5 voice do not end in silence stream, so add one if necessary
                int count = morphTarget.Sequence["Viseme"].Count;
                if (morphTarget.Sequence["Viseme"].ElementAt(count - 1).Morph.Name != "V00")
                {
                    morphTarget.AddList("V00", "Viseme");
                    morphTarget.Sequence["Viseme"].ElementAt(count - 2).Stop(true);
                }
                else
                {
                    morphTarget.Sequence["Viseme"].ElementAt(count - 1).Stop(true);
                    morphTarget.ResetPlayList("Viseme");
                }
#endif
            }
        }

        #endregion

        #region Control Panel delegates
        private List<String> Panel_ModelLoadHandler(String ID)
        {
            List<String> modelList = new List<String>();
            if (String.IsNullOrEmpty(ID))
            {
                ConfigReader.ModelsXML[] modelsXML = worldLoader.Load<ConfigReader.ModelsXML[]>("Models/Models");
                foreach (ModelsXML entry in modelsXML)
                {
                    if (entry.Name != "Ground")
                        modelList.Add(entry.Name);
                }
            }
            else
            {
                bScreencap = true;
                //ModelMeshChanged(ID);
                modelChangedItem.Type = changedEnum.Mesh;
                modelChangedItem.Param.Add(ID);
            }
            return modelList;
        }

        private void ModelMeshChanged(String ID)
        {
            ConfigReader.ModelsXML[] modelsXML = worldLoader.Load<ConfigReader.ModelsXML[]>("Models/Models");
            foreach (ModelsXML entry in modelsXML)
            {
                if (entry.Name == ID)
                {
                    // cleanup first
                    //mainLoader.Dispose();
                    tempLoader.Dispose();
                    //mainLoader = new ContentManager(this.Services, "./Content");
                    tempLoader = new ContentManager(this.Services, "./Content");
                    //modelManager.ContentLoader = mainLoader;

                    modelHook = Load3DModels(entry);
                    if (!String.IsNullOrEmpty(modelHook))
                    {
                        // update camera
                        camera["Main"].ChaseName = modelHook + "BodyA";
                        camera["Sub"].ChaseName = modelHook + "BodyA";
                        camera["Main"].HandlerUpdate = true;
                        camera["Sub"].HandlerUpdate = true;

                        // startup blink morph
                        CSkinnedModel mdl = modelManager.SkinModel[modelHook + "Head"];
                        foreach (CMorphTarget mt in mdl.MorphTargets.Values)
                        {
                            if (mt.Players.ContainsKey("Blink"))
                            {
                                mt.Players["Blink"].ForwardTime = new TimeSpan(0, 0, 0, 0, 50);    //0.05s
                                mt.Players["Blink"].BackwardTime = new TimeSpan(0, 0, 0, 0, 50);   //0.05s
                                mt.Players["Blink"].Duration = new TimeSpan(0, 0, 0, 0, 50);        //0.05s
                                mt.Players["Blink"].RepeatTime = new TimeSpan(0, 0, 10);            //10s
                                mt.Players["Blink"].Start();                                        //start blink at startup
                            }
                        }

                        // startup chat mode
                        if (mdl.Personality != null)
                        {
                            controlPanel.EnableChat(true);

                            String response;
                            if (mdl.Personality.EngineType == Personality.AIType.VERBOT)
                                response = mdl.Personality.GetResponse("_startup");
                            else
                                response = String.Empty;

                            // display response to log
                            if (!String.IsNullOrEmpty(response))
                            {
                                controlPanel.AddLog(response, false);
                                // speak if voice is active: TODO
                                if (avatarVoice != null)
                                    avatarVoice.Speak(controlPanel.LogText, SpeechVoiceSpeakFlags.SVSFlagsAsync);
                            }
                        }
                        else
                        {
                            // no personality, disable chat
                            controlPanel.EnableChat(false);
                        }

                    }
                    break;
                }
            }
        }

        private void Panel_modelClothTypeHandler(String hook, SelectedModel.mtype newtype, SelectedModel.mtype oldtype)
        {
            bScreencap = true;
            //ModelClothTypeChanged(newtype.ToString(), oldtype.ToString());
            modelChangedItem.Type = changedEnum.ClothType;
            modelChangedItem.Param.Add(newtype.ToString());
            modelChangedItem.Param.Add(oldtype.ToString());
        }

        private void ModelClothTypeChanged(String newtype, String oldtype)
        {
            if (bDemo && (newtype == SelectedModel.mtype.Nude.ToString()))
            {
                Forms.Helpers.MessageBox("Nude type is not supported in demo mode");
                if (oldtype == SelectedModel.mtype.Nude.ToString())
                    return;
            }

            if (modelManager.SkinModel.ContainsKey(modelHook + "BodyA"))
            {
                foreach (ModelMesh mesh in modelManager.SkinModel[modelHook + "BodyA"].ModelInfo.Meshes)
                {
                    if ((((oldtype == SelectedModel.mtype.Normal.ToString()) || (newtype == SelectedModel.mtype.Nude.ToString())) && (mesh.Name.Contains("o01_P_Bura"))) ||
                        (((oldtype == SelectedModel.mtype.OnePiece.ToString()) || (newtype == SelectedModel.mtype.Nude.ToString())) && (mesh.Name.Contains("o02_P_Bura"))) ||
                        (((oldtype == SelectedModel.mtype.BodyFit.ToString()) || (newtype == SelectedModel.mtype.Nude.ToString())) && (mesh.Name.Contains("o03_P_Bura"))) ||
                        (((oldtype == SelectedModel.mtype.Bikini.ToString()) || (newtype == SelectedModel.mtype.Nude.ToString())) && (mesh.Name.Contains("o04_P_Bura"))) ||
                        (((oldtype == SelectedModel.mtype.Bikini.ToString()) || (newtype == SelectedModel.mtype.Nude.ToString())) && (mesh.Name.Contains("o04_P_Ribon"))))
                            (mesh.Tag as CMesh).IsVisible = false;
                    if (bDemo && (newtype == SelectedModel.mtype.Nude.ToString())) {
                        newtype = oldtype;
                    }
                    if (((newtype == SelectedModel.mtype.Normal.ToString()) && (mesh.Name.Contains("o01_P_Bura"))) ||
                        ((newtype == SelectedModel.mtype.OnePiece.ToString()) && (mesh.Name.Contains("o02_P_Bura"))) ||
                        ((newtype == SelectedModel.mtype.BodyFit.ToString()) && (mesh.Name.Contains("o03_P_Bura"))) ||
                        ((newtype == SelectedModel.mtype.Bikini.ToString()) && (mesh.Name.Contains("o04_P_Bura"))) ||
                        ((newtype == SelectedModel.mtype.Bikini.ToString()) && (mesh.Name.Contains("o04_P_Ribon"))))
                            (mesh.Tag as CMesh).IsVisible = true;
                    else if (bDemo && (oldtype == SelectedModel.mtype.Nude.ToString())) {
                        if (mesh.Name.Contains("o01_P_Bura") || mesh.Name.Contains("o02_P_Bura") || mesh.Name.Contains("o03_P_Bura") ||
                            mesh.Name.Contains("o04_P_Bura") || mesh.Name.Contains("o04_P_Ribon"))
                            (mesh.Tag as CMesh).IsVisible = false;
                    }

#if false
                    if ((((oldtype == SelectedModel.mtype.Normal.ToString()) || newtype == SelectedModel.mtype.Nude.ToString()) && (mesh.Name.Contains("o01_P_Bura"))) ||
                        (((oldtype == SelectedModel.mtype.OnePiece.ToString()) || newtype == SelectedModel.mtype.Nude.ToString()) && (mesh.Name.Contains("o02_P_Bura"))) ||
                        (((oldtype == SelectedModel.mtype.BodyFit.ToString()) || newtype == SelectedModel.mtype.Nude.ToString()) && (mesh.Name.Contains("o03_P_Bura"))) ||
                        (((oldtype == SelectedModel.mtype.Bikini.ToString()) || newtype == SelectedModel.mtype.Nude.ToString()) && (mesh.Name.Contains("o04_P_Bura"))) ||
                        (((oldtype == SelectedModel.mtype.Bikini.ToString()) || newtype == SelectedModel.mtype.Nude.ToString()) && (mesh.Name.Contains("o04_P_Ribon"))))
                            if (!bDemo || (newtype != SelectedModel.mtype.Nude.ToString()))   // TODO: doesn't work
                                (mesh.Tag as CMesh).IsVisible = false;
                    if (((newtype == SelectedModel.mtype.Normal.ToString()) && (mesh.Name.Contains("o01_P_Bura"))) ||
                        ((newtype == SelectedModel.mtype.OnePiece.ToString()) && (mesh.Name.Contains("o02_P_Bura"))) ||
                        ((newtype == SelectedModel.mtype.BodyFit.ToString()) && (mesh.Name.Contains("o03_P_Bura"))) ||
                        ((newtype == SelectedModel.mtype.Bikini.ToString()) && (mesh.Name.Contains("o04_P_Bura"))) ||
                        ((newtype == SelectedModel.mtype.Bikini.ToString()) && (mesh.Name.Contains("o04_P_Ribon"))))
                            if (!bDemo || (newtype != SelectedModel.mtype.Nude.ToString())) 
                                (mesh.Tag as CMesh).IsVisible = true;
#endif
                }
            }
            if (modelManager.SkinModel.ContainsKey(modelHook + "BodyB"))
            {
                foreach (ModelMesh mesh in modelManager.SkinModel[modelHook + "BodyB"].ModelInfo.Meshes)
                {
                    if ((((oldtype == SelectedModel.mtype.Normal.ToString()) || newtype == SelectedModel.mtype.Nude.ToString()) && (mesh.Name.Contains("o01_P_Pantu"))) ||
                        (((oldtype == SelectedModel.mtype.OnePiece.ToString()) || newtype == SelectedModel.mtype.Nude.ToString()) && (mesh.Name.Contains("o02_P_Pantu"))) ||
                        (((oldtype == SelectedModel.mtype.BodyFit.ToString()) || newtype == SelectedModel.mtype.Nude.ToString()) && (mesh.Name.Contains("o03_P_Pantu"))) ||
                        (((oldtype == SelectedModel.mtype.Bikini.ToString()) || newtype == SelectedModel.mtype.Nude.ToString()) && (mesh.Name.Contains("o04_P_Pantu"))) ||
                        (((oldtype == SelectedModel.mtype.Bikini.ToString()) || newtype == SelectedModel.mtype.Nude.ToString()) && (mesh.Name.Contains("o04_P_Ribon"))))
                        (mesh.Tag as CMesh).IsVisible = false;
                    if (bDemo && (newtype == SelectedModel.mtype.Nude.ToString()))
                    {
                        newtype = oldtype;
                    }
                    if (((newtype == SelectedModel.mtype.Normal.ToString()) && (mesh.Name.Contains("o01_P_Pantu"))) ||
                        ((newtype == SelectedModel.mtype.OnePiece.ToString()) && (mesh.Name.Contains("o02_P_Pantu"))) ||
                        ((newtype == SelectedModel.mtype.BodyFit.ToString()) && (mesh.Name.Contains("o03_P_Pantu"))) ||
                        ((newtype == SelectedModel.mtype.Bikini.ToString()) && (mesh.Name.Contains("o04_P_Pantu"))) ||
                        ((newtype == SelectedModel.mtype.Bikini.ToString()) && (mesh.Name.Contains("o04_P_Ribon"))))
                        (mesh.Tag as CMesh).IsVisible = true;
                    else if (bDemo && (oldtype == SelectedModel.mtype.Nude.ToString()))
                    {
                        if (mesh.Name.Contains("o01_P_Pantu") || mesh.Name.Contains("o02_P_Pantu") || mesh.Name.Contains("o03_P_Pantu") ||
                            mesh.Name.Contains("o04_P_Pantu") || mesh.Name.Contains("o04_P_Ribon"))
                            (mesh.Tag as CMesh).IsVisible = false;
                    }
                }
            }
        }

        private List<String> Panel_modelClothHandler(String hook, bool toLoad, SelectedModel.mtype mType, SelectedModel.ctype cType, String value)
        {
            List<String> newList = new List<string>();

            if (!toLoad)    // return a list of clothing items
            {
                newList.Add("Off");
                newList.Add("Default");

                // special handling for nude:
                // there's no "OFF" for nude and no texture to load
                if (mType == SelectedModel.mtype.Nude)
                {
                    newList.Remove("Off");
                    return newList;
                }

                ConfigReader.TexturesXML[] texturesXML;
                if (!controlPanel.AllowMixed())
                {
                    texturesXML = tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/" + mType.ToString());
                }
                else
                {
                    List<ConfigReader.TexturesXML> texList;
                    texList = tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/Normal").ToList<ConfigReader.TexturesXML>();
                    texList.AddRange(tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/OnePiece").ToList<ConfigReader.TexturesXML>());
                    texList.AddRange(tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/BodyFit").ToList<ConfigReader.TexturesXML>());
                    texList.AddRange(tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/Bikini").ToList<ConfigReader.TexturesXML>());
                    texturesXML = texList.ToArray<ConfigReader.TexturesXML>();
                }
                if ((cType == SelectedModel.ctype.Tops) ||
                    (cType == SelectedModel.ctype.Bottom))
                {
                    foreach (TexturesXML entry in texturesXML)
                        newList.Add(entry.ID);
                }
                if (cType == SelectedModel.ctype.Skirt)
                {
                    texturesXML = tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/Pareo");
                    foreach (TexturesXML entry in texturesXML)
                        newList.Add(entry.ID);
                }

                // return all textures to default
                if (modelManager.SkinModel.ContainsKey(hook + "BodyA"))
                {
                    foreach (ModelMesh mesh in modelManager.SkinModel[hook + "BodyA"].ModelInfo.Meshes)
                    {
                        if (mesh.Name.Contains("Bura") || mesh.Name.Contains("Ribon"))
                            (mesh.Tag as CMesh).CustomTexture = null;
                    }
                }
                if (modelManager.SkinModel.ContainsKey(hook + "BodyB"))
                {
                    foreach (ModelMesh mesh in modelManager.SkinModel[hook + "BodyB"].ModelInfo.Meshes)
                    {
                        if (mesh.Name.Contains("Pantu") || mesh.Name.Contains("Ribon"))
                            (mesh.Tag as CMesh).CustomTexture = null;
                    }
                }
            }
            else
            {
                bScreencap = true;
                //ModelClothChanged(mType.ToString(), cType.ToString(), value);
                modelChangedItem.Type = changedEnum.Cloth;
                modelChangedItem.Param.Add(mType.ToString());
                modelChangedItem.Param.Add(cType.ToString());
                modelChangedItem.Param.Add(value);
            }
            return newList;
        }

        private void ModelClothChanged(String mType, String cType, String value)
        {
            if (cType == SelectedModel.ctype.Tops.ToString())
            {
                if (modelManager.SkinModel.ContainsKey(modelHook + "BodyA"))
                {
                    if (value == "Off")
                    {
                        if (bDemo)
                            Forms.Helpers.MessageBox("Off type is not supported in demo mode.");
                        else
                        {
                            foreach (ModelMesh mesh in modelManager.SkinModel[modelHook + "BodyA"].ModelInfo.Meshes)
                            {
                                if (mesh.Name.Contains("Bura") || mesh.Name.Contains("Ribon"))

                                    (mesh.Tag as CMesh).IsVisible = false;
                            }
                        }
                    }
                    else if (value == "Default")
                    {
                        foreach (ModelMesh mesh in modelManager.SkinModel[modelHook + "BodyA"].ModelInfo.Meshes)
                        {
                            if (mesh.Name.Contains("Bura") || mesh.Name.Contains("Ribon"))
                            {
                                (mesh.Tag as CMesh).IsVisible = false;
                                (mesh.Tag as CMesh).CustomTexture = null;
                                if (((mType == SelectedModel.mtype.Normal.ToString()) && (mesh.Name.Contains("o01_P_Bura"))) ||
                                    ((mType == SelectedModel.mtype.OnePiece.ToString()) && (mesh.Name.Contains("o02_P_Bura"))) ||
                                    ((mType == SelectedModel.mtype.BodyFit.ToString()) && (mesh.Name.Contains("o03_P_Bura"))) ||
                                    ((mType == SelectedModel.mtype.Bikini.ToString()) && (mesh.Name.Contains("o04_P_Bura"))) ||
                                    ((mType == SelectedModel.mtype.Bikini.ToString()) && (mesh.Name.Contains("o04_P_Ribon"))))
                                    (mesh.Tag as CMesh).IsVisible = true;
                            }
                        }
                    }
                    else
                    {
                        ConfigReader.TexturesXML[] texturesXML;
                        if (!controlPanel.AllowMixed())
                        {
                            texturesXML = tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/" + mType.ToString());
                        }
                        else
                        {
                            List<ConfigReader.TexturesXML> texList;
                            texList = tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/Normal").ToList<ConfigReader.TexturesXML>();
                            texList.AddRange(tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/OnePiece").ToList<ConfigReader.TexturesXML>());
                            texList.AddRange(tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/BodyFit").ToList<ConfigReader.TexturesXML>());
                            texList.AddRange(tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/Bikini").ToList<ConfigReader.TexturesXML>());
                            texturesXML = texList.ToArray<ConfigReader.TexturesXML>();
                        }
                        foreach (TexturesXML entry in texturesXML)
                        {
                            if (entry.ID == value)
                            {
                                foreach (ModelMesh mesh in modelManager.SkinModel[modelHook + "BodyA"].ModelInfo.Meshes)
                                {
                                    if (mesh.Name.Contains("Bura") || mesh.Name.Contains("Ribon"))
                                    {
                                        (mesh.Tag as CMesh).IsVisible = false;
                                        (mesh.Tag as CMesh).CustomTexture = null;
                                        if (((mesh.Name.Contains("o01_P_Bura")) && (mType == SelectedModel.mtype.Normal.ToString())) ||
                                            ((mesh.Name.Contains("o02_P_Bura")) && (mType == SelectedModel.mtype.OnePiece.ToString())) ||
                                            ((mesh.Name.Contains("o03_P_Bura")) && (mType == SelectedModel.mtype.BodyFit.ToString())) ||
                                            ((mesh.Name.Contains("o04_P_Bura")) && (mType == SelectedModel.mtype.Bikini.ToString())) ||
                                            ((mesh.Name.Contains("o04_P_Ribon")) && (mType == SelectedModel.mtype.Bikini.ToString())))
                                        {
                                            (mesh.Tag as CMesh).IsVisible = true;
                                            (mesh.Tag as CMesh).CustomTexture = tempLoader.Load<Texture2D>("Models/Common/Texture/" + entry.Name);
                                        }
                                    }
                                }
                                break;
                            }
                        }
                    }
                }
            }
            if (cType == SelectedModel.ctype.Bottom.ToString())
            {
                if (modelManager.SkinModel.ContainsKey(modelHook + "BodyB"))
                {
                    if (value == "Off")
                    {
                        if (bDemo)
                            Forms.Helpers.MessageBox("Off type is not supported in demo mode.");
                        else
                        {
                            foreach (ModelMesh mesh in modelManager.SkinModel[modelHook + "BodyB"].ModelInfo.Meshes)
                            {
                                if (mesh.Name.Contains("Pantu") || mesh.Name.Contains("Ribon"))

                                    (mesh.Tag as CMesh).IsVisible = false;
                            }
                        }
                    }
                    else if (value == "Default")
                    {
                        foreach (ModelMesh mesh in modelManager.SkinModel[modelHook + "BodyB"].ModelInfo.Meshes)
                        {
                            if (mesh.Name.Contains("Pantu") || mesh.Name.Contains("Ribon"))
                            {
                                (mesh.Tag as CMesh).IsVisible = false;
                                (mesh.Tag as CMesh).CustomTexture = null;
                                if (((mType == SelectedModel.mtype.Normal.ToString()) && (mesh.Name.Contains("o01_P_Pantu"))) ||
                                    ((mType == SelectedModel.mtype.OnePiece.ToString()) && (mesh.Name.Contains("o02_P_Pantu"))) ||
                                    ((mType == SelectedModel.mtype.BodyFit.ToString()) && (mesh.Name.Contains("o03_P_Pantu"))) ||
                                    ((mType == SelectedModel.mtype.Bikini.ToString()) && (mesh.Name.Contains("o04_P_Pantu"))) ||
                                    ((mType == SelectedModel.mtype.Bikini.ToString()) && (mesh.Name.Contains("o04_P_Ribon"))))
                                    (mesh.Tag as CMesh).IsVisible = true;
                            }
                        }
                    }
                    else //if (mType == SelectedModel.mtype.Normal)
                    {
                        ConfigReader.TexturesXML[] texturesXML;
                        if (!controlPanel.AllowMixed())
                        {
                            texturesXML = tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/" + mType.ToString());
                        }
                        else
                        {
                            List<ConfigReader.TexturesXML> texList;
                            texList = tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/Normal").ToList<ConfigReader.TexturesXML>();
                            texList.AddRange(tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/OnePiece").ToList<ConfigReader.TexturesXML>());
                            texList.AddRange(tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/BodyFit").ToList<ConfigReader.TexturesXML>());
                            texList.AddRange(tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/Bikini").ToList<ConfigReader.TexturesXML>());
                            texturesXML = texList.ToArray<ConfigReader.TexturesXML>();
                        }
                        foreach (TexturesXML entry in texturesXML)
                        {
                            if (entry.ID == value)
                            {
                                foreach (ModelMesh mesh in modelManager.SkinModel[modelHook + "BodyB"].ModelInfo.Meshes)
                                {
                                    if (mesh.Name.Contains("Pantu") || mesh.Name.Contains("Ribon"))
                                    {
                                        (mesh.Tag as CMesh).IsVisible = false;
                                        (mesh.Tag as CMesh).CustomTexture = null;
                                        if (((mesh.Name.Contains("o01_P_Pantu")) && (mType == SelectedModel.mtype.Normal.ToString())) ||
                                            ((mesh.Name.Contains("o02_P_Pantu")) && (mType == SelectedModel.mtype.OnePiece.ToString())) ||
                                            ((mesh.Name.Contains("o03_P_Pantu")) && (mType == SelectedModel.mtype.BodyFit.ToString())) ||
                                            ((mesh.Name.Contains("o04_P_Pantu")) && (mType == SelectedModel.mtype.Bikini.ToString())) ||
                                            ((mesh.Name.Contains("o04_P_Ribon")) && (mType == SelectedModel.mtype.Bikini.ToString())))
                                        {
                                            (mesh.Tag as CMesh).IsVisible = true;
                                            (mesh.Tag as CMesh).CustomTexture = tempLoader.Load<Texture2D>("Models/Common/Texture/" + entry.Name);
                                        }
                                    }
                                }
                                break;
                            }
                        }
                    }
                }
            }
            if (cType == SelectedModel.ctype.Skirt.ToString())
            {
                if (modelManager.SkinModel.ContainsKey(modelHook + "Pareo"))
                {
                    if (value == "Off")
                    {
                        foreach (ModelMesh mesh in modelManager.SkinModel[modelHook + "Pareo"].ModelInfo.Meshes)
                            (mesh.Tag as CMesh).IsVisible = false;
                    }
                    else if (value == "Default")
                    {
                        foreach (ModelMesh mesh in modelManager.SkinModel[modelHook + "Pareo"].ModelInfo.Meshes)
                        {
                            (mesh.Tag as CMesh).IsVisible = true;
                            (mesh.Tag as CMesh).CustomTexture = null;
                        }
                    }
                    else
                    {
                        ConfigReader.TexturesXML[] texturesXML = tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Texture/Pareo");
                        foreach (TexturesXML entry in texturesXML)
                        {
                            if (entry.ID == value)
                            {
                                foreach (ModelMesh mesh in modelManager.SkinModel[modelHook + "Pareo"].ModelInfo.Meshes)
                                {
                                    (mesh.Tag as CMesh).IsVisible = true;
                                    (mesh.Tag as CMesh).CustomTexture = tempLoader.Load<Texture2D>("Models/Common/Texture/" + entry.Name);
                                }
                                break;
                            }
                        }
                    }
                }
            }
        }

        private List<String> Panel_modelAccessoryHandler(String hook, bool toLoad, SelectedModel.atype type, String value)
        {
            List<String> newList = new List<String>();

            if (!toLoad)
            {
                newList.Add("Off");
                ConfigReader.TexturesXML[] accessoryXML = tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Accessory/" + type.ToString());
                foreach (ConfigReader.TexturesXML entry in accessoryXML)
                    newList.Add(entry.ID);
            }
            else
            {
                bScreencap = true;
                //ModelAccessoryChanged(type.ToString(), value);
                modelChangedItem.Type = changedEnum.Accessory;
                modelChangedItem.Param.Add(type.ToString());
                modelChangedItem.Param.Add(value);
            }

            return newList;
        }

        private void ModelAccessoryChanged(String type, String value)
        {
            if (type == SelectedModel.atype.Feet.ToString())
            {
                if (value == "Off")
                {
                    modelManager.Remove(modelHook + "Feet");
                }
                else
                {
                    ConfigReader.TexturesXML[] accessoryXML = tempLoader.Load<ConfigReader.TexturesXML[]>("Models/Common/Accessory/" + type.ToString());
                    foreach (TexturesXML entry in accessoryXML)
                    {
                        if (entry.ID == value)
                        {
                            // cleanup first if a previous model is loaded
                            String modelID = modelHook + "Feet";
                            if (modelManager.SkinModel.ContainsKey(modelID))
                                modelManager.Remove(modelID);

                            // load accessory model
                            modelManager.Add(modelID, "Models/Common/Accessory/" + entry.Name, ModelsComponent.ModelType.SKINNED);
                            modelManager.SkinModel[modelID].Parent = modelHook + "BodyA";
                            modelManager.ChangeModelOrder(modelID, 100);  // ensure accessories are drawn last
                            // load animation here: copy clipList from BodyB
                            AnimationContainer animationContainer = modelManager.SkinModel[modelID].Animation;
                            animationContainer.animationHandler += new AnimationHandler(animationHandler);
                            animationContainer.CreateClipMap(modelManager.SkinModel[modelHook + "BodyB"].TagInfo.BoneIndices,
                                                             modelManager.SkinModel[modelID].TagInfo.BoneIndices,
                                                             modelManager.SkinModel[modelHook + "BodyB"].Animation.MainPlayer.ClipMap,
                                                             false);
                            animationContainer.ClipList.AddRange(modelManager.SkinModel[modelHook + "BodyB"].Animation.ClipList);
                            animationContainer.defClipListLength = modelManager.SkinModel[modelHook + "BodyB"].Animation.defClipListLength;
                            animationContainer.Add("Main",
                                                    modelManager.SkinModel[modelID].TagInfo,
                                                    modelManager.SkinModel[modelHook + "BodyB"].Animation.MainPlayer.AnimationClipName,
                                                    modelManager.SkinModel[modelHook + "BodyB"].Animation.MainPlayer.AnimationClips,
                                                    modelManager.SkinModel[modelHook + "BodyB"].Animation.MainPlayer.PlaybackMode);
                            animationContainer.MainPlayer.Scale = modelManager.SkinModel[modelHook + "BodyB"].Animation.MainPlayer.Scale;
                            animationContainer.MainPlayer.PlayerInfo.Interpolation = AnimationPlayer.InterpolationMode.Cubic;
                            animationContainer.MainPlayer.Repeat = modelManager.SkinModel[modelHook + "BodyB"].Animation.MainPlayer.Repeat;

                            // synchronize with playing clip
                            // TODO: loop over all animated models
                            modelManager.SkinModel[modelHook + "BodyA"].Animation.Play();
                            modelManager.SkinModel[modelHook + "BodyB"].Animation.Play();
                            modelManager.SkinModel[modelHook + "Pareo"].Animation.Play();
                            animationContainer.Play();
                            break;
                        }
                    }
                }
            }
        }

        private void Panel_modelHairTypeHandler(String hook, SelectedModel.htype newtype, SelectedModel.htype oldtype)
        {
            if (newtype == oldtype) return;
            bScreencap = true;

            //ModelHairChanged(newtype.ToString());
            modelChangedItem.Type = changedEnum.Hair;
            modelChangedItem.Param.Add(newtype.ToString());
            
        }

        private void ModelHairChanged(String param)
        {
            if (modelManager.SkinModel.ContainsKey(modelHook + "Hair"))
                modelManager.Remove(modelHook + "Hair");
            ConfigReader.ModelsXML[] modelsXML = worldLoader.Load<ConfigReader.ModelsXML[]>("Models/Models");
            foreach (ModelsXML entry in modelsXML)
            {
                if (entry.Name == modelHook)
                {
                    Load3DModels(entry, (param == SelectedModel.htype.Advanced.ToString()) ? true : false);
                    break;
                }
            }
        }

        private List<String> Panel_modelAnimationHandler(String hook, bool toLoad, String newValue, String oldValue)
        {
            List<String> newList = new List<string>();
            ConfigReader.AnimationXML[] animationXML = tempLoader.Load<ConfigReader.AnimationXML[]>("Models/Common/Animation/Animations");

            if (!toLoad)    // just get the list of animations available
                            // TODO: supposedly depending on hook, but for now, just accept all
            {
                foreach (AnimationXML entry in animationXML)
                    newList.Add(entry.Name);
            }
            else
            {
                bScreencap = true;
                //ModelAnimationChanged(newValue);
                modelChangedItem.Type = changedEnum.Animation;
                modelChangedItem.Param.Add(newValue);
            }

            return newList;
        }

        private void ModelAnimationChanged(String param)
        {
            if (param == "Default")
            {
                // TODO: fix this, loop
                // redirect animation to start of clipList
                if (modelManager.SkinModel.ContainsKey(modelHook + "BodyA"))
                {
                    AnimationContainer aContainer = modelManager.SkinModel[modelHook + "BodyA"].Animation;
                    aContainer.MainPlayer.AnimationClips = aContainer.ClipList[0].animationClip;
                    aContainer.MainPlayer.AnimationClipName = aContainer.ClipList[0].animationName;
                    aContainer.MainPlayer.Scale = 2.4f / (0.8f * aContainer.ClipList[0].scale);
                    aContainer.MainPlayer.PlaybackMode = (aContainer.ClipList[0].repeat == 0) ? AnimationPlayer.PlaybackMode.LoopForward : AnimationPlayer.PlaybackMode.Forward;
                    aContainer.MainPlayer.Repeat = aContainer.ClipList[0].repeat;
                    aContainer.ClearClipMap(true);
                    aContainer.Play();
                    // clean up clipList
                    if (aContainer.ClipList.Count > aContainer.defClipListLength)
                        aContainer.ClipList.RemoveRange(aContainer.defClipListLength,
                                                        aContainer.ClipList.Count - aContainer.defClipListLength);
                }
                if (modelManager.SkinModel.ContainsKey(modelHook + "BodyB"))
                {
                    AnimationContainer aContainer = modelManager.SkinModel[modelHook + "BodyB"].Animation;
                    aContainer.MainPlayer.AnimationClips = aContainer.ClipList[0].animationClip;
                    aContainer.MainPlayer.AnimationClipName = aContainer.ClipList[0].animationName;
                    aContainer.MainPlayer.Scale = 2.4f / (0.8f * aContainer.ClipList[0].scale);
                    aContainer.MainPlayer.PlaybackMode = (aContainer.ClipList[0].repeat == 0) ? AnimationPlayer.PlaybackMode.LoopForward : AnimationPlayer.PlaybackMode.Forward;
                    aContainer.MainPlayer.Repeat = aContainer.ClipList[0].repeat;
                    aContainer.ClearClipMap(true);
                    aContainer.Play();
                    // clean up clipList
                    if (aContainer.ClipList.Count > aContainer.defClipListLength)
                        aContainer.ClipList.RemoveRange(aContainer.defClipListLength,
                                                        aContainer.ClipList.Count - aContainer.defClipListLength);
                }
                if (modelManager.SkinModel.ContainsKey(modelHook + "Pareo"))
                {
                    AnimationContainer aContainer = modelManager.SkinModel[modelHook + "Pareo"].Animation;
                    // TODO: bug fix: do not load default, load idle instead
                    int animIndex = 0;
                    if (aContainer.ClipList[animIndex].animationName == "Default") animIndex++;
                    aContainer.MainPlayer.AnimationClips = aContainer.ClipList[animIndex].animationClip;
                    aContainer.MainPlayer.AnimationClipName = aContainer.ClipList[animIndex].animationName;
                    aContainer.MainPlayer.Scale = 2.4f / (0.8f * aContainer.ClipList[animIndex].scale);
                    aContainer.MainPlayer.PlaybackMode = (aContainer.ClipList[animIndex].repeat == 0) ? AnimationPlayer.PlaybackMode.LoopForward : AnimationPlayer.PlaybackMode.Forward;
                    aContainer.MainPlayer.Repeat = aContainer.ClipList[animIndex].repeat;
                    aContainer.ClearClipMap(true);
                    aContainer.Play();
                    // clean up clipList
                    if (aContainer.ClipList.Count > aContainer.defClipListLength)
                        aContainer.ClipList.RemoveRange(aContainer.defClipListLength,
                                                        aContainer.ClipList.Count - aContainer.defClipListLength);
                }
                if (modelManager.SkinModel.ContainsKey(modelHook + "Feet"))
                {
                    AnimationContainer aContainer = modelManager.SkinModel[modelHook + "Feet"].Animation;
                    aContainer.MainPlayer.AnimationClips = aContainer.ClipList[0].animationClip;
                    aContainer.MainPlayer.AnimationClipName = aContainer.ClipList[0].animationName;
                    aContainer.MainPlayer.Scale = 2.4f / (0.8f * aContainer.ClipList[0].scale);
                    aContainer.MainPlayer.PlaybackMode = (aContainer.ClipList[0].repeat == 0) ? AnimationPlayer.PlaybackMode.LoopForward : AnimationPlayer.PlaybackMode.Forward;
                    aContainer.MainPlayer.Repeat = aContainer.ClipList[0].repeat;
                    aContainer.CreateClipMap(modelManager.SkinModel[modelHook + "BodyB"].TagInfo.BoneIndices, modelManager.SkinModel[modelHook + "Feet"].TagInfo.BoneIndices, true);
                    aContainer.Play();
                    // clean up clipList
                    if (aContainer.ClipList.Count > aContainer.defClipListLength)
                        aContainer.ClipList.RemoveRange(aContainer.defClipListLength,
                                                        aContainer.ClipList.Count - aContainer.defClipListLength);
                }
            }
            else
            {
                ConfigReader.AnimationXML[] animationXML = tempLoader.Load<ConfigReader.AnimationXML[]>("Models/Common/Animation/Animations");
                foreach (AnimationXML entry in animationXML)
                {
                    if (entry.Name == param)
                    {
                        // TODO: Fix skirt: no associated animation for dance, so hide it
                        if (entry.Name == "Beat")
                        {
                            Forms.Helpers.MessageBox("Skirt currently only supports static animation, unlike the " + Environment.NewLine + 
                                                     "Hair which supports SoftBodyDynamics. As there are no " + Environment.NewLine + 
                                                     "associated dance animation for the skirt, it will be hidden.");
                            ModelClothChanged(String.Empty, SelectedModel.ctype.Skirt.ToString(), "Off");
                        }
                        foreach (AnimationStruct data in entry.Data)
                        {
                            foreach (String mdl in data.Model)
                            {
                                if (modelManager.SkinModel.ContainsKey(modelHook + mdl))
                                {
                                    AnimationContainer aContainer = modelManager.SkinModel[modelHook + mdl].Animation;
                                    // clean up clipList
                                    if (aContainer.ClipList.Count > aContainer.defClipListLength)
                                        aContainer.ClipList.RemoveRange(aContainer.defClipListLength,
                                                                        aContainer.ClipList.Count - aContainer.defClipListLength);

                                    SkinningData tag = tempLoader.Load<SkinningData>("Models/Common/Animation/" + data.ID);
                                    if (tag.BoneIndices.Count != modelManager.SkinModel[modelHook + mdl].TagInfo.BoneIndices.Count)
                                        aContainer.CreateClipMap(tag.BoneIndices, modelManager.SkinModel[modelHook + mdl].TagInfo.BoneIndices, true);
                                    else // assume clipMap is not needed
                                        aContainer.ClearClipMap(true);
                                    for (int i = 0; i < data.Animation.Count; i++)
                                    {
                                        SkinnedModel.AnimationInfo aInfo = new SkinnedModel.AnimationInfo();
                                        aInfo.animationName = param + data.Animation[i].Name;
                                        aInfo.animationClip = tag.AnimationClips[data.Animation[i].Name];
                                        aInfo.scale = (data.Animation[i].Scale != null) ? (float)data.Animation[i].Scale : 1.0f;
                                        aInfo.next = (data.Animation[i].Next != null) ? (int)data.Animation[i].Next - i : 1;
                                        aInfo.repeat = (data.Animation[i].Repeat != null) ? (int)data.Animation[i].Repeat : 1;
                                        aContainer.ClipList.Add(aInfo);
                                    }

                                    // then set animation to top of list
                                    aContainer.MainPlayer.AnimationClips = aContainer.ClipList[aContainer.defClipListLength].animationClip;
                                    aContainer.MainPlayer.AnimationClipName = aContainer.ClipList[aContainer.defClipListLength].animationName;
                                    aContainer.MainPlayer.Scale = 2.4f / (0.8f * aContainer.ClipList[aContainer.defClipListLength].scale);
                                    aContainer.MainPlayer.PlaybackMode = (aContainer.ClipList[aContainer.defClipListLength].repeat == 0) ? AnimationPlayer.PlaybackMode.LoopForward : AnimationPlayer.PlaybackMode.Forward;
                                    aContainer.MainPlayer.Repeat = aContainer.ClipList[aContainer.defClipListLength].repeat;
                                    aContainer.Play();
                                }
                            }
                        }
                        break;
                    }
                }
            }
        }

        private List<string> Panel_modelDanceHandler(string hook, string newValue, string oldValue)
        {
            List<String> newList = new List<string>();
            ConfigReader.AnimationXML[] animationXML = tempLoader.Load<ConfigReader.AnimationXML[]>("Models/Common/Animation/Dance");
            foreach (AnimationXML entry in animationXML)
                newList.Add(entry.Name);
            return newList;
        }

        int danceIndex = 0;
        private void ModelDanceChanged()
        {
            ConfigReader.AnimationXML[] danceXML = tempLoader.Load<ConfigReader.AnimationXML[]>("Models/Common/Animation/Dance");
            int index = new Random().Next(danceXML.Length);
            AnimationXML entry = danceXML[index];
            int repeat = new Random().Next(1, 4);
            foreach (AnimationStruct data in entry.Data)
            {
                foreach (String mdl in data.Model)
                {
                    if (modelManager.SkinModel.ContainsKey(modelHook + mdl))
                    {
                        AnimationContainer aContainer = modelManager.SkinModel[modelHook + mdl].Animation;
                        // clean up clipList
                        if (aContainer.ClipList.Count > aContainer.defClipListLength)
                            aContainer.ClipList.RemoveRange(aContainer.defClipListLength,
                                                            aContainer.ClipList.Count - aContainer.defClipListLength);

                        SkinningData tag = tempLoader.Load<SkinningData>("Models/Common/Animation/" + data.ID);
                        if (tag.BoneIndices.Count != modelManager.SkinModel[modelHook + mdl].TagInfo.BoneIndices.Count)
                            aContainer.CreateClipMap(tag.BoneIndices, modelManager.SkinModel[modelHook + mdl].TagInfo.BoneIndices, true);
                        else // assume clipMap is not needed
                            aContainer.ClearClipMap(true);
                        for (int i = 0; i < data.Animation.Count; i++)
                        {
                            SkinnedModel.AnimationInfo aInfo = new SkinnedModel.AnimationInfo();
                            aInfo.animationName = entry.Name + data.Animation[i].Name;
                            aInfo.animationClip = tag.AnimationClips[data.Animation[i].Name];
                            aInfo.scale = (data.Animation[i].Scale != null) ? (float)data.Animation[i].Scale : 1.0f;
                            aInfo.next = (data.Animation[i].Next != null) ? (int)data.Animation[i].Next - i : 1;
                            //aInfo.repeat = (data.Animation[i].Repeat != null) ? (int)data.Animation[i].Repeat : 1;
                            aInfo.repeat = repeat;
                            aContainer.ClipList.Add(aInfo);
                        }

                        // then set animation to top of list
                        aContainer.MainPlayer.AnimationClips = aContainer.ClipList[aContainer.defClipListLength].animationClip;
                        aContainer.MainPlayer.AnimationClipName = aContainer.ClipList[aContainer.defClipListLength].animationName;
                        aContainer.MainPlayer.Scale = 2.4f / (1.0f * aContainer.ClipList[aContainer.defClipListLength].scale);
                        aContainer.MainPlayer.PlaybackMode = (aContainer.ClipList[aContainer.defClipListLength].repeat == 0) ? AnimationPlayer.PlaybackMode.LoopForward : AnimationPlayer.PlaybackMode.Forward;
                        aContainer.MainPlayer.Repeat = aContainer.ClipList[aContainer.defClipListLength].repeat;
                        aContainer.Play();

                        // if this is BodyA, disable breast physics if/since dance already has breast animation
                        if (mdl == "BodyA")
                        {
                            foreach (Physics.SoftBodyDynamics sbd in modelManager.SkinModel[modelHook + mdl].SoftBodyDynamics.Values)
                                sbd.IsEnabled = true;
                        }

                        // update propertygrid
                        controlPanel.UpdateDance(entry.Name);
                        danceIndex = index;
                    }
                }
            }
        }

        private List<String> Panel_modelMorphHandler(string hook, bool toLoad, String newValue, String oldValue)
        {
            List<String> newList = new List<String>();
            if (!toLoad)
            {
                newList.Add("Base");
                try
                {
                    foreach (CMorphTarget mt in modelManager.SkinModel[modelHook + "Head"].MorphTargets.Values)
                    {
                        foreach (string morphName in mt.Players.Keys)
                            if (!newList.Contains(morphName) && (!morphName.StartsWith("V")))
                                newList.Add(morphName);

                        if (mt.Players.Count > 0)
                        {
                            mt.sequenceHandler += new SequenceHandler(Expr_SequenceHandler);
                            mt.CreatePlayList("Expression");
                        }
                    }
                }
                catch
                {
                }
            }
            else
            {
                //bScreencap = true;
                //modelChangedItem.Type = changedEnum.Expression;
                //modelChangedItem.Param.Add(newValue);
                ModelExpressionChanged(newValue);
            }

            return newList;
        }

        private void ModelExpressionChanged(String param)
        {
            // TODO: cheat a bit here, create a dynamic playlist that resets when "Base" is chosen
            if (param.ToLower().Contains("base"))
            {
                // stop/reset the playlist
                foreach (CMorphTarget mt in modelManager.SkinModel[modelHook + "Head"].MorphTargets.Values)
                {
                    if (mt.Sequence.ContainsKey("Expression"))
                    {
                        int count = mt.Sequence["Expression"].Count;
                        if (count > 0)
                        {
                            mt.Sequence["Expression"].ElementAt(count - 1).Stop(true);
                            // restart "blink" if necessary
                            if (mt.Players.ContainsKey("Blink") && (mt.Players["Blink"].State == MorphPlayer.state.INACTIVE))
                                mt.Players["Blink"].Start();
                        }
                    }
                }
            }
            else if (!param.ToLower().Contains("blink"))
            {
                foreach (CMorphTarget mt in modelManager.SkinModel[modelHook + "Head"].MorphTargets.Values)
                {
                    if (mt.Sequence.ContainsKey("Expression"))
                    {
                        if (!mt.IsEnabled("Expression"))  // this is first in playlist
                            mt.StartPlayList("Expression", true);

                        mt.AddList(param, "Expression");
                        // NB. for dynamic/static sequences, bTime ramp of previous morph and
                        // fTime ramp of next morph must be the same. This is not a limitation
                        // for un-sequenced morphs
                        mt.Sequence["Expression"].Last().ForwardTime = new TimeSpan(0, 0, 0, 0, 500);
                        mt.Sequence["Expression"].Last().BackwardTime = new TimeSpan(0, 0, 0, 0, 500);

                        // stop previous morph
                        int count = mt.Sequence["Expression"].Count;
                        if (count > 1)
                            mt.Sequence["Expression"].ElementAt(count - 2).Stop(true);

                        // start/stop blink
                        if (mt.Players.ContainsKey("Blink"))
                        {
                            if (param.Contains("-CL"))
                                mt.Players["Blink"].Stop(false);
                            else
                                mt.Players["Blink"].Start();
                        }
                    }
                }
            }

#if false
            // first stop all active morphs
            foreach (CMorphTarget mt in modelManager.SkinModel[modelHook + "Head"].MorphTargets.Values)
            {
                foreach (string morphName in mt.Players.Keys)
                {
                    // Special handling of "Blink" here
                    if (morphName.ToLower().Contains("blink"))
                    {
                        if (param.Contains("-CL")) 
                            mt.Players[morphName].Stop(true);
                    }
                    else if (mt.Players[morphName].State == MorphPlayer.state.HOLD)
                        mt.Players[morphName].Stop(true);
                }
            }

            // start "param" morph if param != "base" or "blink"
            if (!param.ToLower().Contains("base") && !param.ToLower().Contains("blink"))
            {
                // start "param" morph
                foreach (CMorphTarget mt in modelManager.SkinModel[modelHook + "Head"].MorphTargets.Values)
                {
                    foreach (string morphName in mt.Players.Keys)
                    {
                        if (morphName == param) 
                        {
                            mt.Players[morphName].ForwardTime = new TimeSpan(0, 0, 0, 0, 500);
                            mt.Players[morphName].BackwardTime = new TimeSpan(0, 0, 0, 0, 250);
                            mt.Players[morphName].Start();
                            break;
                        }
                    }
                }

                // restart "blink" if necessary
                foreach (CMorphTarget mt in modelManager.SkinModel[modelHook + "Head"].MorphTargets.Values)
                {
                    if (mt.Players.ContainsKey("Blink") && !param.Contains("-CL"))
                        mt.Players["Blink"].Start();                                        //start blink at startup
                }

            }
#endif
        }

        private List<string> Panel_modelVisemeHandler(string hook, string newValue, string oldValue)
        {
            List<String> newList = new List<string>();
            try
            {
                foreach (CMorphTarget mt in modelManager.SkinModel[modelHook + "Head"].MorphTargets.Values)
                {
                    foreach (string morphName in mt.Players.Keys)
                        if (!newList.Contains(morphName) && (morphName.StartsWith("V")))
                            newList.Add(morphName);

                    if (mt.Players.Count > 0)
                    {
                        mt.sequenceHandler += new SequenceHandler(Viseme_SequenceHandler);
                        mt.CreatePlayList("Viseme");
                    }
                }
            }
            catch
            {
            }
            return newList;
        }

        /// <summary>
        /// Sequence Handler callback
        /// Called when an event is triggered by morph sequence player
        /// </summary>
        /// <param name="sname">Name of sequence currently being played</param>
        /// <param name="mname">Name of active morph</param>
        /// <param name="status">Play status (START, NEXT, DONE)</param>
        private void Expr_SequenceHandler(CMorphTarget morphTarget, string sname, string mname, PlayStatus status)
        {
            if (sname != "Expression") return;
            if (status == PlayStatus.DONE)
            {
                morphTarget.ResetPlayList(sname);
            }
            else
            {
            }
        }
        private void Viseme_SequenceHandler(CMorphTarget morphTarget, string sname, string mname, PlayStatus status)
        {
            if (sname != "Viseme") return;
            if (status == PlayStatus.DONE)
            {
                morphTarget.ResetPlayList(sname);
            }
            else
            {
            }
        }


        #endregion

        #endregion
    }
}
